package classifier;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import javax.jws.WebParam.Mode;

import extractors.Extractor;
import extractors.SimilarityMeasure;

import primitive.Article;
import primitive.SortPair;
import utils.SortStack;
import utils.SortStack.mode_t;

public class NearestNeighborAlgorithm {
	
	public enum metric_t { EUCLIDEAN, CHEBYSHEV, MANHATTAN };
	enum method_t { EXTRACTOR, SIMILARITY_MEASURE };
	
	private int k = 5;
	private metric_t metric;
	private List<HashMap<String, List<Double>>> testing_vectors;
	private List<HashMap<String, List<Double>>> training_vectors;
	private List<Article> articles_list;
	private List<Article> training_data;
	
	private Extractor extractor;
	private SimilarityMeasure similarity;
	private method_t active_method;
	
	int correct = 0;
	int all = 0;
	
	HashMap<String, Integer> correct_by_category = new HashMap<>();
	HashMap<String, Integer> all_by_category = new HashMap<>();
	
	public void init(int k) {
		//
	}
	
	public void set_testing_vectors(List<HashMap<String, List<Double>>> vectors) {
		testing_vectors = vectors;
		System.out.println("Testing set has been added, size: " + vectors.size());
	}
	
	public void set_training_vectors(List<HashMap<String, List<Double>>> vectors) {
		training_vectors = vectors;
		System.out.println("Training set has been added, size: " + vectors.size());
		
		System.out.println("This method is deprecated, please do not use it anymore.");
	}
	
	public void setExtractor(Extractor e) {
		//
		active_method = method_t.EXTRACTOR;
		extractor = e;
		
		training_vectors = e.getTrainingSet();
	}
	
	public void setSimilarityMeasure(SimilarityMeasure s) {
		//
		active_method = method_t.SIMILARITY_MEASURE;
		similarity = s;
		
		training_data = s.getTrainingData();
	}
	
	public void set_testing_data(List<Article> articles_list) {
		this.articles_list = articles_list;
	}
	
	public void run() {
		try {
			System.out.println("Starting classification");
			if (active_method == method_t.EXTRACTOR) {
				System.out.println("Testing set size: " + articles_list.size() + ", training set size: " + training_vectors.size());
			} else if (active_method == method_t.SIMILARITY_MEASURE) {
				System.out.println("Testing set size: " + articles_list.size() + ", training set size: " + training_data.size());
			}
			System.out.println("Computation start at: " + new Date());
			
			SortStack stack = new SortStack(k);
			
			// Chose proper application logic
			if (active_method == method_t.EXTRACTOR) {
				
				
				// First loop for accessing to testin' data
				for (Article article : articles_list) {
					String category_tes = article.getValue("people");
					// Compute feature vector for this data
					List<Double> vector_tes = extractor.extraction(article);
					
					// Now lets compare this testing data wit training datas and compute distances
					for (int j = 0; j < training_vectors.size(); ++j) {
						String category_tre = training_vectors.get(j).entrySet().iterator().next().getKey();
						List<Double> vector_tre = training_vectors.get(j).entrySet().iterator().next().getValue();
						
						double dis = distance(vector_tes, vector_tre);
						
						SortPair res = new SortPair();
						res.first = dis;
						res.second = category_tre;
						
						stack.append(res);
					}
					
					//System.out.println(category_tes + " ::: " + stack.get_most_popular());
					if (category_tes.equals(stack.get_most_popular())) {
						correct++;
						
						if (correct_by_category.containsKey(category_tes)) {
							int c = correct_by_category.get(category_tes) + 1;
							correct_by_category.put(category_tes, c);
						} else {
							correct_by_category.put(category_tes, 1);
						}
					}
					all++;
					if (all_by_category.containsKey(stack.get_most_popular())) {
						int c = all_by_category.get(stack.get_most_popular()) + 1;
						all_by_category.put(stack.get_most_popular(), c);
					} else {
						all_by_category.put(stack.get_most_popular(), 1);
					}
					//if (all_by_category.containsKey(category_tes)) {
					//	int c = all_by_category.get(category_tes) + 1;
					//	all_by_category.put(category_tes, c);
					//} else {
					//	all_by_category.put(category_tes, 1);
					//}
					stack.clear();
				}
				
				
				
			} else if (active_method == method_t.SIMILARITY_MEASURE) {
				
				System.out.println("UNDER IMPLEMENTATION!");
				
				String tre_category = "";
				String tes_category = "";
				
				// Change the way which SortStack is working 
				stack.setMode(mode_t.DESC);
				int z = 0;
				
				// First loop for accesing to testing data
				for (Article article : articles_list) {
					// Now we gonna comapre this testing article with all ones from training set
					for (int j = 0; j < training_data.size(); ++j) {
						//
						Article tre_article = training_data.get(j);
						tre_category = tre_article.getValue("people");		// <---------- kind of label
						tes_category = article.getValue("people");	// <------ kind of label
						
						double s = similarity.similarity(article, tre_article);
						
						SortPair res = new SortPair();
						res.first = s;
						res.second = tre_category;
						
						stack.append(res);
					}
					
					
					// Debug info
					System.out.println(tes_category + " " + stack.get_most_popular());
					
					// Compute results of classification
					if (tes_category.equals(stack.get_most_popular())) {
						correct++;
						
						if (correct_by_category.containsKey(tes_category)) {
							int c = correct_by_category.get(tes_category) + 1;
							correct_by_category.put(tes_category, c);
						} else {
							correct_by_category.put(tes_category, 1);
						}
					}
					all++;
					//if (all_by_category.containsKey(stack.get_most_popular())) {
					//	int c = all_by_category.get(stack.get_most_popular()) + 1;
					//	all_by_category.put(stack.get_most_popular(), c);
					//} else {
					//	all_by_category.put(stack.get_most_popular(), 1);
					//	}		/* not shure, but this solution is buggy */
					if (all_by_category.containsKey(tes_category)) {
						int c = all_by_category.get(tes_category) + 1;
						all_by_category.put(tes_category, c);
					} else {
						all_by_category.put(tes_category, 1);
					}
						

					stack.clear();
					z++;
					//System.out.println("" + z + " / " + articles_list.size());
					
				}
				
				
			} else {
				
				
				System.out.println("Error! Extractor neither Similarity Measure has not been set!");
				
				
			}
			
			
			
			System.out.println("Computations stop at: " + new Date());
		} catch (Exception e) {
			System.out.println("ERROR! " + e.getMessage());
		}
	}
	
	public void runOLD() {
		System.out.println("Starting classification");
		//System.out.println("Testing set size: " + testing_vectors.size() + ", training set size: " + training_vectors.size());
		System.out.println("Testing set size: " + testing_vectors.size() + ", training set size: " + training_vectors.size());
		System.out.println("Computation start at: " + new Date());
		
		//--------------------------
		
		SortStack stack = new SortStack(k);
		
		// First loop for accessing to testing's data
		for (int i = 0; i < testing_vectors.size(); ++i) {
			String category_tes = testing_vectors.get(i).entrySet().iterator().next().getKey();
			List<Double> vector_tes = testing_vectors.get(i).entrySet().iterator().next().getValue();
			
			// Now lets compare this testing data with training datas and compute distances
			for (int j = 0; j < training_vectors.size(); ++j) {
				String category_tre = training_vectors.get(j).entrySet().iterator().next().getKey();
				List<Double> vector_tre = training_vectors.get(j).entrySet().iterator().next().getValue();
				
				double dis = distance(vector_tes, vector_tre);
				//System.out.println(dis + " " + category_tes + " " + category_tre);
				
				SortPair res = new SortPair();
				res.first = dis;
				res.second = category_tre;
				
				stack.append(res);
				
			}

			//System.out.println(category_tes + " ::: " + stack.get_most_popular());
			if (category_tes.equals(stack.get_most_popular())) {
				correct++;
				
				if (correct_by_category.containsKey(category_tes)) {
					int c = correct_by_category.get(category_tes) + 1;
					correct_by_category.put(category_tes, c);
				} else {
					correct_by_category.put(category_tes, 1);
				}
			}
			all++;
			if (all_by_category.containsKey(stack.get_most_popular())) {
				int c = all_by_category.get(stack.get_most_popular()) + 1;
				all_by_category.put(stack.get_most_popular(), c);
			} else {
				all_by_category.put(stack.get_most_popular(), 1);
			}
			stack.clear();
			//System.out.println(category_tre);
		}
		
		//--------------------------
		
		System.out.println("Computations stop at: " + new Date());
	}
	
	public void show_results() {
		System.out.println("results");
		
		System.out.println("Correct = " + correct + ", all = " + all);
		System.out.println(correct_by_category);
		System.out.println(all_by_category);
		
		System.out.println("Efectiveness: " + (double)correct / (double)all);
		Iterator<String> it = all_by_category.keySet().iterator();
		while(it.hasNext()) {
			String key = it.next();
			if (key != null ) {
				if (correct_by_category.get(key) != null && all_by_category.get(key) != null ) {
			System.out.println("Effectivenes in " + key + " = " + (double)correct_by_category.get(key) / (double)all_by_category.get(key));
				}
			}
		}
	}
	
	public void set_active_metric(metric_t metric) {
		this.metric = metric;
	}
	
	// Metrics
	public double distance(List<Double> x, List<Double> y) {
		// Forward data to proper method
		if (metric == metric_t.EUCLIDEAN) {
			return euclidean_distance(x, y);
		} else if (metric == metric_t.CHEBYSHEV) {
			return chebyshev_distance(x, y);
		} else if (metric == metric_t.MANHATTAN) {
			return manhattan_distance(x, y);
		} else {
			return euclidean_distance(x, y);
		}
	}
	
	public double euclidean_distance(List<Double> x, List<Double> y) {
		double sum = 0.0;
		
		for (int i = 0; i < x.size(); ++i) {
			sum += (x.get(i) - y.get(i)) * (x.get(i) - y.get(i));
		}
		
		return Math.sqrt(sum);
	}
	
	public double chebyshev_distance(List<Double> x, List<Double> y) {
		double max = 0.0;
		double tmp = 0.0;
		
		for (int i = 0; i < x.size(); ++i) {
			tmp = Math.abs(x.get(i) - y.get(i));
			if (tmp > max) {
				max = tmp;
			}
		}
		
		return max;
	}
	
	public double manhattan_distance(List<Double> x, List<Double> y) {
		double sum = 0.0;
		
		for (int i = 0; i < x.size(); ++i) {
			sum += Math.abs(x.get(i) - y.get(i));
		}
		
		return sum;
	}
}
